# ---------- 简介 ---------- #
# FlowSensor.py
# 赵宗义, xinshengzzy@foxmail.com, 2020.12.15
# 在这个实现版本中, 主表中计数器的宽度为16位, 辅表中
# 摘要, 计数器和截图的宽度都为16字节, 并且我们在主表
# 中存放完整的流标识符
# ------------------ #
from 标准库 import 累积序列
from simulators.BaseSimulator import BaseSimulator
import mytools
from 文件工具 import 备份数据


class MBucket:
    def __init__(self):
        self.flowID = 0
        self.count = 0
        self.status = 0

    def empty(self):
        if 0 == self.flowID and 0 == self.count:
            return True
        else:
            return False

    def init(self, flowID, count):
        self.flowID = flowID
        self.count = count
        self.status = count

    def increCount(self):
        self.count += 1
        self.status = self.count

    def decreStatus(self):
        self.status -= 1

    def getRecord(self):
        return (self.flowID, self.count)


class ABucket:
    def __init__(self):
        self.digest = 0
        self.count = 0
        self.status = 0

    def reset(self):
        self.digest = 0
        self.count = 0
        self.status = 0

    def empty(self):
        if 0 == self.digest and 0 == self.count:
            return True
        else:
            return False

    def init(self, digest, count, status):
        self.digest = digest
        self.count = count
        self.status = status

    def increCount(self):
        self.count += 1
        self.status += 1

    def decreStatus(self):
        self.status -= 1


class PromotionRecord:
    def __init__(self, 包序号, 索引, 标识符, 计数值):
        self.包序号 = 包序号
        self.索引 = 索引
        self.标识符 = 标识符
        self.计数值 = 计数值

    def getContent(self):
        return (self.包序号, self.索引, self.标识符, self.计数值)

    def getRecord(self):
        return (self.标识符, self.计数值)


class FlowSensor(BaseSimulator):
    def __init__(self, 内存=1.0): # 内存的单位为MB
        super(FlowSensor, self).__init__()
        tmp = 内存*1024*1024/23.0
        self.表长1 = int(tmp/3)
        self.表长2 = int(tmp/3)
        self.表长3 = int(tmp/3)
        self.表长4 = int(tmp)
        # tmp = 内存*1024*1024/57.0
        # self.表长1 = int(tmp)
        # self.表长2 = int(tmp)
        # self.表长3 = int(tmp)
        # self.表长4 = int(tmp)
        self.小流替换次数 = 0
        self.不活跃流替换次数 = 0
        # ---------- promotion延迟 ---------- #
        self.延迟 = 0
        self.等待队列 = []
        # -------------------- #

        self.M = []
        self.A = []

        for i in range(self.表长1):
            self.M.append(MBucket())  # [flowID, count, status]
        for i in range(self.表长2):
            self.M.append(MBucket())
        for i in range(self.表长3):
            self.M.append(MBucket())
        for i in range(self.表长4):
            self.A.append(ABucket())  # (digest, count)

    def 处理数据包(self, 标识符, 时间戳):
        # ---------- 首先处理promotion ---------- #
        if len(self.等待队列) > 0:
            序号, 索引, 标识符, 计数值 = self.等待队列[0].getContent()
            if self.数据包数 >= 序号:
                del self.等待队列[0]
                self.导出流记录(self.M[索引].getRecord())
                self.M[索引].init(标识符, 计数值)
        # -------------------- #
        偏移量 = 累积序列([0, self.表长1, self.表长2])
        idx1 = mytools.hash1(标识符.encode('utf-8')) % self.表长1 + 偏移量[0]
        idx2 = mytools.hash2(标识符.encode('utf-8')) % self.表长2 + 偏移量[1]
        idx3 = mytools.hash3(标识符.encode('utf-8')) % self.表长3 + 偏移量[2]
        idx4 = mytools.hash4(标识符.encode('utf-8')) % self.表长4
        摘要 = self.计算摘要(标识符)

        cmin = 0xffffffff
        cidx = -1
        smin = 0xffffffff
        sidx = -1
        match = False
        for idx in [idx1, idx2, idx3]:
            if not match:
                if self.M[idx].empty():
                    self.M[idx].init(标识符, 1)
                    match = True
                elif self.M[idx].flowID == 标识符:
                    self.M[idx].increCount()
                    match = True
                else:
                    self.M[idx].decreStatus()
                    if self.M[idx].count < cmin:
                        cmin = self.M[idx].count
                        cidx = idx
                    if self.M[idx].status < smin:
                        smin = self.M[idx].status
                        sidx = idx
            else:
                self.M[idx].decreStatus()

        if not match:
            if self.A[idx4].empty() or 摘要 != self.A[idx4].digest:
                self.A[idx4].init(摘要, 1, smin)
            else:
                self.A[idx4].increCount()
                if self.A[idx4].count > cmin:
                    self.小流替换次数 += 1
                    self.promote_flow(cidx, 标识符, self.A[idx4].count)
                    self.A[idx4].reset()
                elif self.A[idx4].status > smin:
                    self.不活跃流替换次数 += 1
                    self.promote_flow(sidx, 标识符, self.A[idx4].count)
                    self.A[idx4].reset()
        else:
            self.A[idx4].decreStatus()
        return 0

    def 在线流长查询(self, 标识符):
        流长 = 0
        偏移量 = 累积序列([0, self.表长1, self.表长2])
        idx1 = mytools.hash1(标识符.encode('utf-8')) % self.表长1 + 偏移量[0]
        idx2 = mytools.hash2(标识符.encode('utf-8')) % self.表长2 + 偏移量[1]
        idx3 = mytools.hash3(标识符.encode('utf-8')) % self.表长3 + 偏移量[2]
        idx4 = mytools.hash4(标识符.encode('utf-8')) % self.表长4
        摘要 = self.计算摘要(标识符)
        for idx in [idx1, idx2, idx3]:
            if 标识符 == self.M[idx].flowID:
                流长 = max(流长, self.M[idx].count)
        if 摘要 == self.A[idx4].digest:
            流长 = max(流长, self.A[idx].count)
        return 流长

    def promote_flow(self, index, 标识符, count):
        # self.导出流记录(self.M[index].getRecord())
        # self.M[index].init(标识符, count)
        self.等待队列.append(PromotionRecord(self.数据包数 + self.延迟, index, 标识符, count))

    def 下载数据平面(self):
        流集 = dict()
        for item in self.M:
            if not item.empty():
                标识符, 计数值 = item.getRecord()
                if 标识符 not in 流集:
                    流集[标识符] = 0
                流集[标识符] += 计数值
        return 流集

    def 备份(self, 文件名):
        # ---------- 处理等待队列 ---------- #
        while len(self.等待队列) > 0:
            self.导出流记录(self.等待队列[0].getRecord())
            del self.等待队列[0]
        # ------------------- #
        备份 = dict()
        备份["流记录集"] = self.获取流记录集()
        备份["导出次数"] = self.导出次数
        # print("导出次数:%d, 小流替换次数:%d, 不活跃流替换次数:%d" %
        #       (self.导出次数, self.小流替换次数, self.不活跃流替换次数))
        self.带宽消耗 = (self.导出次数*15*8.0)/self.时间跨度
        # print("带宽消耗:%f" % self.带宽消耗)
        备份["带宽消耗"] = self.带宽消耗
        备份数据(备份, 文件名)
